package com.myzl.coal.service.protocol.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.myzl.coal.client.BaseHandler;
import com.myzl.coal.client.CacheData;
import com.myzl.coal.domain.CommandRecord;
import com.myzl.coal.domain.Device;
import com.myzl.coal.domain.DeviceProtocol;
import com.myzl.coal.dto.CmdCallBackDTO;
import com.myzl.coal.dto.CmdDTO;
import com.myzl.coal.dto.SendCmdDTO;
import com.myzl.coal.service.ICommandRecordService;
import com.myzl.coal.service.IDeviceService;
import com.myzl.coal.service.protocol.CmdService;
import com.myzl.coal.utils.ModBusCommandUtil;
import com.myzl.common.constant.HttpStatus;
import com.myzl.common.constant.ProtocolConstants;
import com.myzl.common.enums.DeviceCommandEnum;
import com.myzl.common.enums.DeviceProtocolEnum;
import com.myzl.common.enums.DeviceTypeEnum;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


/**
 * @ClassName: ProtocolService
 * @Author: wzb
 * @Date: 2022/10/30 16:37
 * @Description:
 */
@Service
@Slf4j
@Data
public class CmdServiceImpl implements CmdService {

    public final ICommandRecordService commandRecordService;
    public final StringRedisTemplate redisTemplate;
    public final CacheData cacheData;
    public final IDeviceService deviceService;
    public final MongoTemplate mongoTemplate;


    @Override
    public boolean success(SendCmdDTO dto) {
        redisTemplate.opsForHash().delete(getKey(ProtocolConstants.COMMAND, dto.getDeviceId()), dto.getMessageId());
        String callBackCmd = null;
        if (dto instanceof CmdCallBackDTO) {
            CmdCallBackDTO callBackDTO = (CmdCallBackDTO) dto;
            callBackCmd = callBackDTO.getCallBackCmd();
        }
        return commandRecordService.success(dto.getMessageId(), callBackCmd);
    }

    @Override
    public boolean fail(SendCmdDTO dto) {
        redisTemplate.opsForHash().delete(getKey(ProtocolConstants.COMMAND, dto.getDeviceId()), dto.getMessageId());
        String callBackCmd = null;
        if (dto instanceof CmdCallBackDTO) {
            CmdCallBackDTO callBackDTO = (CmdCallBackDTO) dto;
            callBackCmd = callBackDTO.getCallBackCmd();
        }
        return commandRecordService.fail(dto.getMessageId(), callBackCmd);
    }

    @Override
    public SendCmdDTO getSendCmd(Integer deviceId, String key) {
        List<Object> entries = redisTemplate.opsForHash().values(getKey(ProtocolConstants.COMMAND, deviceId));
        Map<String, SendCmdDTO> cmdDTOMap = entries.stream().map(e -> JSONObject.parseObject(e.toString(), SendCmdDTO.class)).collect(Collectors.toMap(SendCmdDTO::getKey, Function.identity(), (a, b) -> a));

        SendCmdDTO sendCmdDTO = cmdDTOMap.get(key);
        return sendCmdDTO;
    }

    @Override
    public List<SendCmdDTO> getSendCmd() {

        Set<String> keys = redisTemplate.keys(ProtocolConstants.COMMAND + "*");
        List<SendCmdDTO> collect = keys.stream().flatMap(e -> {
                    List<Object> values = redisTemplate.opsForHash().values(e);
                    return values.stream();
                }
        ).map(e -> JSONObject.parseObject(e.toString(), SendCmdDTO.class)).collect(Collectors.toList());
        return collect;
    }


    @Override
    public void cmdTimeOut() {
        List<SendCmdDTO> sendCmdDTOS = getSendCmd();
        sendCmdDTOS.forEach(v -> {
            LocalDateTime sendTime = v.getSendTime();
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime dateTimeBefore = now.minusSeconds(30);

            if (dateTimeBefore.isAfter(sendTime)) {
                fail(v);
            }
        });
    }

    @Override
    public void sendCmd(SendCmdDTO sendCmdDTO) {
        sendCmdDTO.setSendTime(LocalDateTime.now());

        CommandRecord commandRecord = commandRecordService.insertCommandRecord(sendCmdDTO);
        redisTemplate.opsForHash().put(getKey(ProtocolConstants.COMMAND, sendCmdDTO.getDeviceId()), sendCmdDTO.getMessageId(), JSON.toJSONString(sendCmdDTO));

        Integer deviceId = sendCmdDTO.getDeviceId();

        Device device = deviceService.getById(deviceId);
        String ip = device.getIp();
        Integer port = device.getPort();
        if (!StringUtils.hasText(ip) || Objects.isNull(port)) {
            log.info("deviceId:{},设备ip:{} port:{} 不全", deviceId, ip, port);
            return;
        }
        sendCmdDTO.setIp(ip);
        sendCmdDTO.setPort(port);

        int sendStatus = BaseHandler.send(sendCmdDTO);
        if (!Objects.equals(HttpStatus.SUCCESS, sendStatus)) {
            fail(sendCmdDTO);
        } else {
            commandRecordService.commandResult(commandRecord.getId());
        }
    }


    @Override
    public SendCmdDTO read(CmdDTO cmdDTO) {
        Integer deviceType = cmdDTO.getDeviceType();
        Integer deviceId = cmdDTO.getDeviceId();
        int address = cmdDTO.getAddress();
        int count = cmdDTO.getCount();

        DeviceProtocol deviceProtocol = cacheData.getDeviceProtocolMap(deviceType);
        SendCmdDTO sendCmdDTO;
        switch (DeviceProtocolEnum.getValue(deviceProtocol.getDeviceProtocol())) {
            case MODBUS:
                sendCmdDTO = ModBusCommandUtil.readMore(deviceType, deviceId, address, count);
                break;
            default:
                sendCmdDTO = ModBusCommandUtil.readMore(deviceType, deviceId, address, count);
                break;
        }
        if (StringUtils.hasLength(cmdDTO.getRemark())) {
            sendCmdDTO.setRemark(cmdDTO.getRemark());
        } else {
            Map<Integer, DeviceCommandEnum> deviceCommandEnumMap = Arrays.stream(DeviceCommandEnum.class.getEnumConstants()).collect(Collectors.toMap(DeviceCommandEnum::getCode, Function.identity()));
            DeviceCommandEnum deviceCommandEnum = deviceCommandEnumMap.get(address);
            sendCmdDTO.setRemark(deviceCommandEnum.getInfo());
        }
        sendCmd(sendCmdDTO);
        return sendCmdDTO;
    }
    @Override
    @Async("threadPoolTaskExecutor")
    public SendCmdDTO asyncWrite(CmdDTO cmdDTO) {
        return write(cmdDTO);
    }

    @Override
    public SendCmdDTO write(CmdDTO cmdDTO) {
        Integer deviceType = cmdDTO.getDeviceType();
        Integer deviceId = cmdDTO.getDeviceId();
        int address = cmdDTO.getAddress();
        List<Integer> data = cmdDTO.getData();

        SendCmdDTO sendCmdDTO;
        DeviceTypeEnum deviceTypeEnum = DeviceTypeEnum.getValue(deviceType);
        switch (deviceTypeEnum) {
            case DOUBLE_ORDER:
                sendCmdDTO = ModBusCommandUtil.writeCoilsMore(deviceType, deviceId, address, data);
                break;
            case SIEVE_PLC:
                sendCmdDTO = ModBusCommandUtil.writeRegistersMore(deviceType, deviceId, address, data);
                break;
            default:
                sendCmdDTO = ModBusCommandUtil.writeRegistersMore(deviceType, deviceId, address, data);
                break;

        }
        Map<Integer, DeviceCommandEnum> deviceCommandEnumMap = Arrays.stream(DeviceCommandEnum.class.getEnumConstants()).collect(Collectors.toMap(DeviceCommandEnum::getCode, Function.identity(),(a,b)->a));
        DeviceCommandEnum deviceCommandEnum = deviceCommandEnumMap.get(address);
        sendCmdDTO.setRemark(deviceCommandEnum.getInfo());
        sendCmd(sendCmdDTO);
        return sendCmdDTO;
    }

    public String getKey(String prefix, Integer deviceId) {
        return prefix + deviceId;
    }
}
